"""Functions for user targetting"""

from pyre.core import types, actor
from pyre.view import los

def directional(term, camera, cls=actor.Actor):
    """
    directional(Terminal, Camera, type): Coordinate
    Targets in a user provided direction until either a wall or an Actor of the 
    given type is found. 
    """

    key = term.get_key()
    delta = types.Direction.key_to_dir(key)
    if delta and delta != types.Direction.ORIGIN:
        pos = camera.pos + delta
        world = camera.world
        while world.passable_at(pos) and world.get_actor_at(pos, cls) is None:
            pos += delta
        return pos


def free(term, camera, cls=None):
    """
    free(Terminal, Camera, type): Coordinate
    Allows the user to freely select any tile within the Camera field of view
    """

    term.save_buffer()
    pos = types.Coordinate.copy(camera.pos)
    fov = camera.calc_fov()

    key = '\0'
    while key != 't':
        term.recall_buffer()
        camera.draw_relative(term, pos, types.ColoredChar('*'))
        term.refresh_screen()

        key = term.get_key()
        delta = types.Direction.key_to_dir(key)
        if delta is not None:
            update = pos + delta
            if update in fov:
                pos = update

    term.recall_buffer()
    term.refresh_screen()
    return pos


def _find_targets(camera, cls, include_camera):
    world = camera.world
    targets = []
    for pos in camera.calc_fov():
        if world.get_actor_at(pos, cls) is not None:
            targets.append(pos)
    if not include_camera and camera.pos in targets:
        targets.remove(camera.pos)
    return targets


def select(term, camera, cls=actor.Actor, include_camera=False):
    """
    select(Terminal, Camera, type): Coordinate
    Allows the user to choose a target using directional keys to toggle between
    targets of the given class
    """

    targets = _find_targets(camera, cls, include_camera)
    if targets:
        term.save_buffer()
        curr = targets[0]
        key = '\0'

        while key != 't':
            term.recall_buffer()
            camera.draw_relative(term, curr, types.ColoredChar('*'))
            term.refresh_screen()

            key = term.get_key()
            delta = types.Direction.key_to_dir(key)
            if delta is not None:
                dirs_to = curr.directions_to
                cands = [pos for pos in targets if delta in dirs_to(pos)]
                if cands:
                    curr = min(cands, key=curr.distance_to)

        term.recall_buffer()
        term.refresh_screen()
        return curr


def closest(term, camera, cls=actor.Actor, include_camera=False):
    """
    closest(Terminal, Camera, type): Coordinate
    Selects the closest target of the given type to the Camera position
    """

    targets = _find_targets(camera, cls, include_camera)
    if targets:
        return min(targets, key=lambda target: camera.pos.steps_to(target))


def letter(term, camera, cls=actor.Actor, include_camera=False):
    """
    letter(Terminal, Camera, type): Coordinate
    Replaces all potential targets with a letter and allows user to select
    """

    targets = _find_targets(camera, cls, include_camera)

    term.save_buffer()
    for i, pos in enumerate(targets, ord('a')):
        camera.draw_relative(term, pos, types.ColoredChar(chr(i)))
    term.refresh_screen()

    choice = ord(term.get_key()) - ord('a')
    term.recall_buffer()
    term.refresh_screen()

    if 0 <= choice < len(targets):
        return targets[choice]


def beam(term, camera, cls=None):
    """
    free(Terminal, Camera, type): Coordinate
    Allows the user to freely select any tile within the Camera field of view
    """

    term.save_buffer()
    pos = None if cls is None else closest(term, camera, cls, False)
    if not pos:
        pos = types.Coordinate.copy(camera.pos)
    fov = camera.calc_fov()

    key = '\0'
    while key != 't':
        term.recall_buffer()
        for beam_pos in los.bresenham(camera.world, camera.pos, pos):
            tile = camera.world.look(beam_pos).recolored(types.Color.SMOKE)
            camera.draw_relative(term, beam_pos, tile)
        term.refresh_screen()

        key = term.get_key()
        delta = types.Direction.key_to_dir(key)
        if delta:
            update = pos + delta
            if update in fov:
                pos = update

    term.recall_buffer()
    term.refresh_screen()

    path = los.bresenham(camera.world, camera.pos, pos)
    if cls is None:
        return path[-1]
    else:
        for pos in path[1:]: # if len(path) == 0, pos remains targeted tile
            if camera.world.get_actor_at(pos, cls) is not None:
                break # pos remains first tile in path with targeted actor
        return pos

